package com.trg.flex.search
{
	import com.trg.flex.FlexUtils;
	
	[RemoteClass(alias="com.trg.search.Filter")]
	[DefaultProperty("value")]
	[Bindable]
	public class Filter
	{
		public static const OP_EQUAL : int = 0;
		public static const OP_NOT_EQUAL : int = 1;
		public static const OP_LESS_THAN : int = 2;
		public static const OP_GREATER_THAN : int = 3;
		public static const OP_LESS_OR_EQUAL : int = 4;
		public static const OP_GREATER_OR_EQUAL : int = 5;
		public static const OP_LIKE : int = 6;
		public static const OP_ILIKE : int = 7;
		public static const OP_IN : int = 8;
		public static const OP_NOT_IN : int = 9;
		public static const OP_NULL : int = 10;
		public static const OP_NOT_NULL : int = 11;
		public static const OP_EMPTY : int = 12;
		public static const OP_NOT_EMPTY : int = 13;
		public static const OP_AND : int = 100;
		public static const OP_OR : int = 101;
		public static const OP_NOT : int = 102;
		public static const OP_SOME : int = 200;
		public static const OP_ALL : int = 201;
		public static const OP_NONE : int = 202;
		
		public var property : String;
		public var value : Object;
		public var operator : int;
		
		[Inspectable(enumeration="==,!=,<,>,<=,>=,like,ilike,in,not in,null,not null,empty,not empty,and,or,not,some,all,none", defaultValue="==")]
		public function set op(value:String):void {
			if (value == "==") {
				operator = OP_EQUAL;
			} else if (value == "!=") {
				operator = OP_NOT_EQUAL;
			} else if (value == "<") {
				operator = OP_LESS_THAN;
			} else if (value == ">") {
				operator = OP_GREATER_THAN;
			} else if (value == "<=") {
				operator = OP_LESS_OR_EQUAL;
			} else if (value == "<=") {
				operator = OP_GREATER_OR_EQUAL;
			} else if (value.toLowerCase() == "like") {
				operator = OP_LIKE;
			} else if (value.toLowerCase() == "ilike") {
				operator = OP_ILIKE;
			} else if (value.toLowerCase() == "in") {
				operator = OP_IN;
			} else if (value.toLowerCase() == "not in") {
				operator = OP_NOT_IN;
			} else if (value.toLowerCase() == "null") {
				operator = OP_NULL;
			} else if (value.toLowerCase() == "not null") {
				operator = OP_NOT_NULL;
			} else if (value.toLowerCase() == "empty") {
				operator = OP_EMPTY;
			} else if (value.toLowerCase() == "not empty") {
				operator = OP_NOT_EMPTY;
			} else if (value.toLowerCase() == "and") {
				operator = OP_AND;
			} else if (value.toLowerCase() == "or") {
				operator = OP_OR;
			} else if (value.toLowerCase() == "not") {
				operator = OP_NOT;
			} else if (value.toLowerCase() == "some") {
				operator = OP_SOME;
			} else if (value.toLowerCase() == "all") {
				operator = OP_ALL;
			} else if (value.toLowerCase() == "none") {
				operator = OP_NONE;
			} else {
				throw new Error("Invalid Filter operator '" + value + "'");
			}
		}
		
		public function Filter(property:String=null, value:Object=null, operator:int=OP_EQUAL) {
			this.property = property;
			this.value = value;
			this.operator = operator;
		}
		
		public static function equal(property:String, value:Object):Filter {
			return new Filter(property, value, OP_EQUAL);
		}
		public static function notEqual(property:String, value:Object):Filter {
			return new Filter(property, value, OP_NOT_EQUAL);
		}
		public static function lessThan(property:String, value:Object):Filter {
			return new Filter(property, value, OP_LESS_THAN);
		}
		public static function greaterThan(property:String, value:Object):Filter {
			return new Filter(property, value, OP_GREATER_THAN);
		}
		public static function lessOrEqual(property:String, value:Object):Filter {
			return new Filter(property, value, OP_LESS_OR_EQUAL);
		}
		public static function greaterOrEqual(property:String, value:Object):Filter {
			return new Filter(property, value, OP_GREATER_OR_EQUAL);
		}
		public static function like(property:String, value:Object):Filter {
			return new Filter(property, value, OP_LIKE);
		}
		public static function iLike(property:String, value:Object):Filter {
			return new Filter(property, value, OP_ILIKE);
		}
		public static function In(property:String, ...value:Array):Filter {
			if (value.length == 1 && FlexUtils.isCollection(value[0])) {
				value = FlexUtils.collectionToArray(value[0]);
			}
			return new Filter(property, value, OP_IN);
		}
		public static function notIn(property:String, ...value:Array):Filter {
			if (value.length == 1 && FlexUtils.isCollection(value[0])) {
				value = FlexUtils.collectionToArray(value[0]);
			}
			return new Filter(property, value, OP_NOT_IN);
		}
		public static function isNull(property:String):Filter {
			return new Filter(property, null, OP_NULL);
		}
		public static function notNull(property:String):Filter {
			return new Filter(property, null, OP_NOT_NULL);
		}
		public static function isEmpty(property:String):Filter {
			return new Filter(property, null, OP_EMPTY);
		}
		public static function notEmpty(property:String):Filter {
			return new Filter(property, null, OP_NOT_EMPTY);
		}
		public static function and(...filters:Array):Filter {
			return new Filter("AND", filters, OP_AND);
		}
		public static function or(...filters:Array):Filter {
			return new Filter("OR", filters, OP_OR);
		}
		public static function not(filter:Filter):Filter {
			return new Filter("NOT", filter, OP_NOT);
		}
		public static function some(property:String, filter:Filter):Filter {
			return new Filter(property, filter, OP_SOME);
		}
		public static function all(property:String, filter:Filter):Filter {
			return new Filter(property, filter, OP_ALL);
		}
		public static function none(property:String, filter:Filter):Filter {
			return new Filter(property, filter, OP_NONE);
		}
		
		public function doFilter(o:*):Boolean {
			var filter:Filter;
			
			switch (operator) {
				case OP_EQUAL:
					return FlexUtils.getNestedProperty(o, property) == value;
				case OP_NOT_EQUAL:
					return FlexUtils.getNestedProperty(o, property) != value;
				case OP_GREATER_THAN:
					return value == null || FlexUtils.getNestedProperty(o, property) > value;
				case OP_LESS_THAN:
					return value == null || FlexUtils.getNestedProperty(o, property) < value;
				case OP_GREATER_OR_EQUAL:
					return value == null || FlexUtils.getNestedProperty(o, property) >= value;
				case OP_LESS_OR_EQUAL:
					return value == null || FlexUtils.getNestedProperty(o, property) <= value;
				case OP_IN:
					o = FlexUtils.getNestedProperty(o, property);
					if (!(value is Array) || value.length == 0) return false;
					for each (var item:* in value) {
						if (o == item) return true;
					}
					return false;
				case OP_NOT_IN:
					o = FlexUtils.getNestedProperty(o, property);
					if (!(value is Array) || value.length == 0) return true;
					for each (var item:* in value) {
						if (o == item) return false;
					}
					return true;
				case OP_LIKE:
				case OP_ILIKE:
					if (value == null) return true;
					o = FlexUtils.getNestedProperty(o, property);
					if (o == null) return false;
					var exp:String = (operator == OP_ILIKE) ? value.toString().toLowerCase() : value.toString();
					exp = exp.replace(/[\\\.\*\+\?\[\]\{\}\(\)\/\^\$]/g, "\\$&");
					exp = exp.replace(/%/g, ".*");
					exp = exp.replace(/_/g, ".");
					exp = "^" + exp + "$";
					var regEx:RegExp = new RegExp(exp, "i");
					return regEx.test((operator == OP_ILIKE) ? o.toString().toLowerCase() : o.toString());
				case OP_NULL:
					return FlexUtils.getNestedProperty(o, property) == null;
				case OP_NOT_NULL:
					return FlexUtils.getNestedProperty(o, property) != null;
				case OP_EMPTY:
				case OP_NOT_EMPTY:
					var val:* = FlexUtils.getNestedProperty(o, property);
					val = (val == null || (isNaN(val) && !(val is Boolean) && !val)) ? true : false;
					return operator == OP_EMPTY ? val : !val;			
				case OP_AND:
					if (value is Array) {
						for each (filter in value) {
							if (filter != null && !filter.doFilter(o)) return false;
						}
					}
					return true;
				case OP_OR:
					if (value is Array) {
						for each (filter in value) {
							if (filter != null && filter.doFilter(o)) return true;
						}
						return false;
					} else {
						return true;
					}
				case OP_NOT:
					if (!value is Filter)
						throw new Error("A Filter with the NOT operator must have another Filter for the value.");
					return !value.doFilter(0);
				case OP_ALL:
				case OP_SOME:
				case OP_NONE:
					throw new Error("ALL, SOME and NONE operators not currently supported in local searching.");
				default:
					throw new Error("Filter has invalid operator");
			}
		}
	}
}